package org.summerframework.component.security.permissions.service.impl;

import com.baomidou.mybatisplus.core.conditions.Wrapper;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.google.common.util.concurrent.ThreadFactoryBuilder;
import org.summerframework.component.mybatisplus.AbstractBasePlusService;
import org.summerframework.component.security.RoleResultCode;
import org.summerframework.component.security.events.PermissionsChangeEvent;
import org.summerframework.component.security.permissions.model.dao.UserPermissionsDAO;
import org.summerframework.component.security.permissions.model.domain.UserPermissionsDO;
import org.summerframework.component.security.permissions.service.UserPermissionsRoleRelService;
import org.summerframework.component.security.permissions.service.UserPermissionsService;
import org.summerframework.component.security.permissions.service.dto.UserPermissionsDTO;
import org.summerframework.component.security.permissions.service.dto.UserPermissionsQueryDTO;
import org.summerframework.component.security.permissions.service.dto.UserPermissionsRoleRelDTO;
import org.summerframework.core.base.dto.BaseDO;
import org.summerframework.core.exception.WebException;
import org.summerframework.core.util.OptionalUtils;
import org.summerframework.core.util.SpringContextUtil;
import org.apache.commons.collections4.CollectionUtils;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;

import javax.annotation.Resource;
import java.util.*;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.stream.Collectors;

@Service("userPermissionsService")
public class UserPermissionsServiceImpl extends AbstractBasePlusService<UserPermissionsDAO, UserPermissionsDTO, UserPermissionsQueryDTO, UserPermissionsDO> implements UserPermissionsService {
    @Resource
    private UserPermissionsDAO userPermissionsDAO;

    @Value("${application.name}")
    private String applicationName;

    @Value("${spring.role.max-count:#{2}}")
    private Integer maxCount;

    @Override
    public Wrapper<UserPermissionsDO> getWrapper(UserPermissionsQueryDTO query) {
        return Wrappers.<UserPermissionsDO>lambdaQuery()
                .orderBy(Objects.nonNull(query.getOrderByCreateTime()), query.getOrderByCreateTime() == null ? false : query.getOrderByCreateTime(), UserPermissionsDO::getCreateTime)
                .orderBy(Objects.nonNull(query.getOrderBySortIndex()), query.getOrderBySortIndex() != null ? query.getOrderBySortIndex() : false, UserPermissionsDO::getSortIndex)
                .orderBy(Objects.nonNull(query.getOrderByUpdateTime()), query.getOrderByUpdateTime() != null ? query.getOrderByUpdateTime() : false, UserPermissionsDO::getUpdateTime)
                .orderBy(Objects.nonNull(query.getOrderById()), query.getOrderById() != null ? query.getOrderById() : false, UserPermissionsDO::getId)
                .eq(Objects.nonNull(query.getIsSystem()), UserPermissionsDO::getIsSystem, query.getIsSystem())
                .eq(Objects.nonNull(query.getCreateBy()), UserPermissionsDO::getCreateBy, query.getCreateBy())
                .like(StringUtils.isNotBlank(query.getSearchCode()), UserPermissionsDO::getCode, query.getSearchCode())
                .eq(StringUtils.isNotBlank(query.getCode()), UserPermissionsDO::getCode, query.getCode())
                .eq(Objects.nonNull(query.getUpdateBy()), UserPermissionsDO::getUpdateBy, query.getUpdateBy())
                .like(StringUtils.isNotBlank(query.getSearchName()), UserPermissionsDO::getName, query.getSearchName())
                .eq(StringUtils.isNotBlank(query.getName()), UserPermissionsDO::getName, query.getName())
                .eq(Objects.nonNull(query.getType()), UserPermissionsDO::getType, query.getType())
                .eq(Objects.nonNull(query.getParentId()), UserPermissionsDO::getParentId, query.getParentId())
                .eq(Objects.nonNull(query.getStatus()), UserPermissionsDO::getStatus, query.getStatus());
    }

    @Resource
    private UserPermissionsRoleRelService userPermissionsRoleRelService;

    private ExecutorService executors = new ThreadPoolExecutor(10, 100,
            0L, TimeUnit.MILLISECONDS,
            new LinkedBlockingQueue<>(), new ThreadFactoryBuilder().setNameFormat("user_permissions_server_%s").build());


    @Override
    public UserPermissionsDTO insert(UserPermissionsDTO baseDTO) {
        UserPermissionsDTO temp = selectByCode(baseDTO.getCode());

        if (temp != null) {
            baseDTO.setId(temp.getId());

            updateById(baseDTO);
        } else {
            super.insert(baseDTO);
        }

        userPermissionsRoleRelService.insertRepairParentId(baseDTO.getParentId(), baseDTO.getId());

        executors.execute(() -> SpringContextUtil.publishEvent(new PermissionsChangeEvent(BASE_OP.INSERT, baseDTO.getId())));


        return baseDTO;
    }

    @Override
    public Integer updateById(UserPermissionsDTO baseDTO) {
        boolean publish = false;
        UserPermissionsDTO temp = OptionalUtils.get(selectById(baseDTO.getId()));

        if (baseDTO.getParentId() != null && baseDTO.getParentId().equals(baseDTO.getId())) {
            throw new WebException(RoleResultCode.ERROR_PERMISSIONS_PARENT_ID_NOT_SELF);
        }

        if (baseDTO.getStatus() != null && !temp.getStatus().equals(baseDTO.getStatus())) {
            UserPermissionsDTO update = new UserPermissionsDTO();
            if (baseDTO.getStatus() == 0) { //当为停用时, 更新所有下级为停用
                UserPermissionsRoleRelDTO userPermissionsRoleRelDTO = new UserPermissionsRoleRelDTO();
                userPermissionsRoleRelDTO.setStatus(baseDTO.getStatus());
                selectChildByParentId(new ArrayList<>(), baseDTO.getId()).forEach(status -> {
                    if (status.getId().equals(baseDTO.getId())) {
                        return;
                    }
                    userPermissionsRoleRelDTO.setPermissionsId(status.getId());
                    userPermissionsRoleRelService.updateById(userPermissionsRoleRelDTO);

                    update.setId(status.getId());
                    update.setStatus(0);
                    super.updateById(update);
                });
                userPermissionsRoleRelDTO.setPermissionsId(baseDTO.getId());
                userPermissionsRoleRelService.updateById(userPermissionsRoleRelDTO);
                publish = true;
            } else if (baseDTO.getStatus() == 1) { //当为启用时, 更新所有上级菜单与下级为按钮的权限设置为启用
                UserPermissionsQueryDTO query = new UserPermissionsQueryDTO();
                query.setIsPagination(false);
                query.setParentId(baseDTO.getId());
                query.setType(2);

                List<UserPermissionsDTO> userPermissionsDTOS = selectByQuery(query);
                userPermissionsDTOS.addAll(selectParentById(new ArrayList<>(), baseDTO.getId()));

                userPermissionsDTOS.forEach(status -> {
                    if (status.getId().equals(baseDTO.getId())) {
                        return;
                    }
                    UserPermissionsRoleRelDTO userPermissionsRoleRelDTO = new UserPermissionsRoleRelDTO();
                    userPermissionsRoleRelDTO.setPermissionsId(baseDTO.getId());
                    userPermissionsRoleRelDTO.setStatus(baseDTO.getStatus());
                    userPermissionsRoleRelService.updateById(userPermissionsRoleRelDTO);

                    update.setId(status.getId());
                    update.setStatus(1);
                    super.updateById(update);
                });
                publish = true;
            } else {
                baseDTO.setStatus(null);
            }
        }

        if (StringUtils.isNotBlank(baseDTO.getName())) {
            //当和上次一样, 则不更新
            if (baseDTO.getParentId() != null && baseDTO.getParentId().equals(temp.getId())) {
                baseDTO.setParentId(null);
            }

            if (baseDTO.getParentId() != null) {
                List<UserPermissionsDTO> userPermissionsDTOS = selectChildByParentId(new ArrayList<>(), temp.getId());

                //不能添加至自己的子集下面, 会死循环
                if (userPermissionsDTOS.stream().map(UserPermissionsDTO::getId).anyMatch(baseDTO.getParentId()::equals)) {
                    throw new WebException(RoleResultCode.ERROR_PERMISSIONS_PARENT_ID_NOT_CHILD);
                }

                Map<Long, List<UserPermissionsDTO>> collect = userPermissionsDTOS.stream().filter(status -> status.getType() == 1).collect(Collectors.groupingBy(UserPermissionsDTO::getParentId, Collectors.toList()));

                List<UserPermissionsDTO> parentUserPermissions = selectParentById(new ArrayList<>(), baseDTO.getParentId());

                //父级不能超过3级(系统也算一级)
                if ((temp.getType() == 1 && parentUserPermissions.size() > maxCount) || temp.getType() == 2 && parentUserPermissions.size() > maxCount) {
                    throw new WebException(RoleResultCode.ERROR_PERMISSIONS_PARENT_ID_TOW_LEVEL);
                }


                //包含自己加上父级不能超过3级(系统也算一级)
                Integer count = getMaxCount(collect, temp.getId(), 0);
                if ((temp.getType() == 1 && count + parentUserPermissions.size() > maxCount) || temp.getType() == 2 && count + parentUserPermissions.size() > maxCount) {
                    throw new WebException(RoleResultCode.ERROR_PERMISSIONS_PARENT_ID_AND_CHILD_TOW_LEVEL);
                }
            }

            userPermissionsRoleRelService.updateById(baseDTO);

            publish = true;
        }

        Integer count = super.updateById(baseDTO);

        if (publish) {
            executors.execute(() -> SpringContextUtil.publishEvent(new PermissionsChangeEvent(BASE_OP.UPDATE, baseDTO.getId())));
        }

        return count;
    }

    /**
     * 查询子集
     *
     * @param list     原list
     * @param parenrId 上级id
     * @return 所有子集
     */
    private List<UserPermissionsDTO> selectChildByParentId(List<UserPermissionsDTO> list, Long parenrId) {
        List<UserPermissionsDTO> permissionsList = selectByParentId(parenrId);

        list.addAll(permissionsList);

        if (CollectionUtils.isNotEmpty(permissionsList)) {
            permissionsList.forEach(permissions -> selectChildByParentId(list, permissions.getId()));
        }

        return list;
    }

    private Integer getMaxCount(Map<Long, List<UserPermissionsDTO>> collect, Long parentId, Integer count) {
        if (collect.get(parentId) == null) {
            return count;
        }
        AtomicInteger atomicInteger = new AtomicInteger(count);
        Integer finalCount = atomicInteger.incrementAndGet();

        collect.get(parentId).forEach(userPermissionsDTO -> {
            int temp = getMaxCount(collect, userPermissionsDTO.getId(), finalCount);

            if (atomicInteger.get() < temp) {
                atomicInteger.set(temp);
            }
        });

        return atomicInteger.get();
    }

    @Override
    public UserPermissionsDTO selectByCode(String code) {
        return selectOne(UserPermissionsQueryDTO.builder().code(code).build());
    }

    @Override
    public List<UserPermissionsDTO> selectParentById(List<UserPermissionsDTO> list, Long id) {
        if (id.equals(0L)) {
            return list;
        }
        UserPermissionsDTO userPermissionsDTO = OptionalUtils.get(selectById(id));

        list.add(userPermissionsDTO);

        if (!userPermissionsDTO.getParentId().equals(0L)) {
            selectParentById(list, userPermissionsDTO.getParentId());
        }

        return list;
    }

    @Override
    public List<UserPermissionsDTO> selectByParentId(Long parentId) {
        UserPermissionsQueryDTO query = new UserPermissionsQueryDTO();
        query.setParentId(parentId);
        query.setIsPagination(false);

        return selectByQuery(query);
    }

    @Override
    public void repairData(Map<Long, List<UserPermissionsDTO>> groupParentId) {
        new ArrayList<>(groupParentId.keySet()).forEach(parentId -> {
            repairData(parentId, groupParentId);
        });
    }

    @Override
    public void repairData(Long parentId, Map<Long, List<UserPermissionsDTO>> groupParentId) {
        if (parentId.equals(0L)) {
            return;
        }
        UserPermissionsDTO userPermissionsDTO = OptionalUtils.get(selectById(parentId));

        List<UserPermissionsDTO> parents = groupParentId.get(userPermissionsDTO.getParentId());
        if (CollectionUtils.isEmpty(parents) || parents.stream().noneMatch(parent -> parent.getId().equals(parentId))) {
            //添加父节点数据
            groupParentId.computeIfAbsent(userPermissionsDTO.getParentId(), key -> new ArrayList<>()).add(userPermissionsDTO);
            repairData(userPermissionsDTO.getParentId(), groupParentId);
        }
    }

    @Override
    public List<UserPermissionsDTO> selectByQuery(UserPermissionsQueryDTO query) {
        if (query.getIsLazy() != null) query.setOrderBySortIndex(true);

        List<UserPermissionsDTO> userPermissionsDTOS = super.selectByQuery(query);

        if (query.getIsLazy() != null && query.getIsLazy() == 0) {
            userPermissionsDTOS.forEach(userPermissionsDTO -> {
                if (userPermissionsDTO.getChild() != null) {
                    query.setParentId(userPermissionsDTO.getId());
                    userPermissionsDTO.setChild(selectByQuery(query));
                }
            });
        }

        return userPermissionsDTOS;
    }

    @Override
    public UserPermissionsDTO fromDO2DTO(BaseDO basePO) {
        UserPermissionsDTO userPermissionsDTO = super.fromDO2DTO(basePO);
        if (userPermissionsDTO == null) {
            return null;
        }

        UserPermissionsQueryDTO query = new UserPermissionsQueryDTO();
        query.setParentId(basePO.getId());

        if (userPermissionsDTO.getParentId().equals(0L)) {
            userPermissionsDTO.setParentName(applicationName);
        } else {
            UserPermissionsDO userPermissionsPO = this.baseDAO.selectById(userPermissionsDTO.getParentId());

            if (userPermissionsPO != null) {
                userPermissionsDTO.setParentName(userPermissionsPO.getName());
            }
        }

        if (selectCountByQuery(query) > 0) {
            userPermissionsDTO.setChild(Collections.emptyList());
            query.setType(1);
            userPermissionsDTO.setLeaf(selectCountByQuery(query) == 0);
        } else {
            userPermissionsDTO.setLeaf(true);
        }

        return userPermissionsDTO;
    }

    @Override
    public Integer deleteById(Long id) {
        userPermissionsRoleRelService.deleteByObjectId(id);
        final Integer count = super.deleteById(id);
        executors.execute(() -> SpringContextUtil.publishEvent(new PermissionsChangeEvent(BASE_OP.DELETE, id)));
        return count;
    }
}
